package io.gitee.zhangbinhub.acp.boot.websocket

import io.gitee.zhangbinhub.acp.boot.component.BootLogAdapter
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import io.gitee.zhangbinhub.acp.core.common.NetTools
import org.noear.solon.annotation.Inject
import org.noear.solon.core.util.MultiMap
import org.noear.solon.net.websocket.WebSocket
import org.noear.solon.net.websocket.WebSocketListener
import java.nio.ByteBuffer
import java.util.concurrent.ConcurrentHashMap

abstract class AcpWebSocketServer : WebSocketListener {
    @Inject
    protected var logAdapter: LogAdapter = BootLogAdapter()
    protected open var clientIdKey: String = "clientId"

    private fun getClientId(socket: WebSocket): String? {
        val queryMap = socket.paramMap()
        var clientId: String? = queryMap[clientIdKey]
        if (clientId == null) {
            clientId = socket.attr(clientIdKey)
        }
        return clientId
    }

    fun getHandle(socket: WebSocket): WebSocketServerHandle? = getClientId(socket)?.let { clientId ->
        acpWebSocketInstanceMap[clientId]
    }

    override fun onOpen(socket: WebSocket) {
        val paramMap = socket.paramMap()
        val clientId = getClientId(socket)
        val clientIp = NetTools.getIpFromInetAddresses(socket.remoteAddress().address)
        if (clientId == null) {
            logAdapter.error("websocket client id is null")
            socket.close()
        } else {
            val handle = WebSocketServerHandle(logAdapter, socket, clientId, clientIp, paramMap)
            acpWebSocketInstanceMap[clientId] = handle
            sessionOpen(handle)
        }
    }

    override fun onClose(socket: WebSocket) {
        val handle = getHandle(socket)
        acpWebSocketInstanceMap.remove(handle?.clientId)
        sessionClose(handle)
    }

    override fun onMessage(socket: WebSocket, text: String?) {
        receiveMessage(getHandle(socket)!!, text)
    }

    override fun onMessage(socket: WebSocket, binary: ByteBuffer?) {
        receiveMessage(getHandle(socket)!!, binary)
    }

    override fun onError(socket: WebSocket, error: Throwable?) {
        sessionError(getHandle(socket), error)
    }

    abstract fun sessionOpen(handle: WebSocketServerHandle)
    abstract fun sessionClose(handle: WebSocketServerHandle?)
    abstract fun sessionError(handle: WebSocketServerHandle?, throwable: Throwable?)
    abstract fun receiveMessage(handle: WebSocketServerHandle, message: String?)
    abstract fun receiveMessage(handle: WebSocketServerHandle, message: ByteBuffer?)

    companion object {
        private val log: LogAdapter = BootLogAdapter()
        private val acpWebSocketInstanceMap: ConcurrentHashMap<String, WebSocketServerHandle> =
            ConcurrentHashMap<String, WebSocketServerHandle>()

        @JvmStatic
        fun getInstance(clientId: String): WebSocketServerHandle? = acpWebSocketInstanceMap[clientId]

        @JvmStatic
        fun getInstanceList(): List<WebSocketServerHandle> =
            acpWebSocketInstanceMap.map { entry -> entry.value }.apply {
                if (this.isEmpty()) {
                    log.warn("WebSocket does not have any clients!")
                }
            }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         * @param clientId 客户端唯一标识
         */
        @JvmStatic
        fun sendMessageToClient(message: String, clientId: String): Boolean = getInstance(clientId).let { instance ->
            if (instance != null) {
                instance.sendMessage(message)
            } else {
                log.error("WebSocket client[$clientId] is not found!")
                false
            }
        }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         * @param clientId 客户端唯一标识
         */
        @JvmStatic
        fun sendMessageToClient(message: ByteBuffer, clientId: String): Boolean =
            getInstance(clientId).let { instance ->
                if (instance != null) {
                    instance.sendMessage(message)
                } else {
                    log.error("WebSocket client[$clientId] is not found!")
                    false
                }
            }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         */
        @JvmStatic
        fun sendMessageToClient(message: String) {
            getInstanceList().forEach { instance ->
                if (!instance.sendMessage(message)) {
                    log.error("WebSocket client[${instance.clientId}] send message failed!")
                }
            }
        }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         */
        @JvmStatic
        fun sendMessageToClient(message: ByteBuffer) {
            getInstanceList().forEach { instance ->
                if (!instance.sendMessage(message)) {
                    log.error("WebSocket client[${instance.clientId}] send message failed!")
                }
            }
        }
    }

    class WebSocketServerHandle(
        private val logAdapter: LogAdapter?,
        @JvmField
        var socket: WebSocket,
        @JvmField
        var clientId: String,
        @JvmField
        var clientIp: String?,
        @JvmField
        var paramMap: MultiMap<String>
    ) {
        fun close() {
            socket.close()
        }

        fun sendMessage(message: String): Boolean = if (beforeSendMessage()) {
            try {
                socket.send(message)
                true
            } catch (e: Exception) {
                logAdapter?.error(e.message, e)
                false
            }
        } else {
            false
        }

        fun sendMessage(message: ByteBuffer): Boolean = if (beforeSendMessage()) {
            try {
                socket.send(message)
                true
            } catch (e: Exception) {
                logAdapter?.error(e.message, e)
                false
            }
        } else {
            false
        }

        private fun beforeSendMessage(): Boolean = if (socket.isValid) {
            true
        } else {
            logAdapter?.error("WebSocket session is null or closed!")
            false
        }
    }
}